// Dependencies
#include "glExt.h"
#include "Point2.h"
#include "Rect2.h"
#include "Color4.h"
#include "Polygon2.h"
#include "Tessellation2.h"
#include "Platform.h"
#include "GlPlatform.h"

namespace glExt {
	// Because other compilation units (other .cpp files) will not know the definition for emitVertex at compile time 
	// from just including the header, they will rely on the linker to provide the definitions at link time.
	// By explicitly instantiating templates for specific types here, this compilation unit will provide
	// those definitions to the linker.  The advantage to this approach is that compilation units using glExt will
	// not need to know about Windows or OpenGL.

	// Explicit template instantiation for emitVertex with type double/int respectively
	// (An attempt to call emitVertex for other types will give an unresolved external symbol error)
	template<> void emitVertex( const Point2_t<float>& pt ) { glVertex2f( pt.x, pt.y ); }
	template<> void emitVertex( const Point2_t<double>& pt ) { glVertex2d( pt.x, pt.y ); }
	template<> void emitVertex( const Point2_t<int>& pt) { glVertex2i( pt.x, pt.y ); }

	// Template definition for emitRect
	template<class Type>
	void emitRect( const Rect2_t<Type>& rect ) {
		for ( int idx = 0; idx < 4; ++idx )
			emitVertex( rect.corner(idx) );
	}

	// Explicit template instantiation for emitRect with type double/int respectively
	template void emitRect( const Rect2_t<double>& );
	template void emitRect( const Rect2_t<float>& );
	template void emitRect( const Rect2_t<int>& );

	
	void emitColor( const Color4& color ) {
		glColor4f( color.r, color.g, color.b, color.a );
	}

	void renderPolygon( const Polygon2& poly, double width, const Color4& color, bool outline, bool normals, bool wireframe ) {
		Color4 solid = color;
		solid.a = 1.f;

		// Interior
		glEnable( GL_BLEND );
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		emitColor( color );
		poly.tessellation().eachPrimitive( [&] ( const Tessellation2::Primitive& prim ) {
			glBegin( prim.mode ); {
				std::for_each( prim.vertices.begin(), prim.vertices.end(), emitVertex<double> );
			} glEnd();
		} );
		glDisable( GL_BLEND );

		// Interior (wire-frame)
		if ( wireframe ) {
			emitColor( solid );
			glBegin( GL_LINES ); {
				poly.tessellation().eachTriangle( [&] ( const Point2& a, const Point2& b, const Point2& c ) {
					emitVertex( a ); emitVertex( b );
					emitVertex( b ); emitVertex( c );
					emitVertex( c ); emitVertex( a );
				} );
			} glEnd();
		}

		// Outlines
		FOR ( const Contour2& c, poly.contours() ) {
			const std::vector<Point2>& vertices = c.vertices();
			const std::vector<Vector2>& edgeNormals = c.edgeNormals();
			const std::vector<Vector2>& vertexNormals = c.vertexNormals();

			if ( outline ) {
				glBegin( GL_TRIANGLE_STRIP ); {
					emitColor( solid );
					for ( size_t idxA = vertices.size()-1, idxB = 0; idxB < vertices.size(); idxA = idxB, ++idxB ) {
						emitVertex( vertices[idxB] );
						emitVertex( vertices[idxB] + -vertexNormals[idxB] * (width/vertexNormals[idxB].projectionOnto(edgeNormals[idxB]).length()) );
					}

					int idxA = vertices.size()-1;
					int idxB = 0;
					emitVertex( vertices[idxB] );
					emitVertex( vertices[idxB] + -vertexNormals[idxB] * (width/vertexNormals[idxB].projectionOnto(edgeNormals[idxB]).length()) );
				} glEnd();
			}

			// Normals
			if ( normals ) {
				glBegin( GL_LINES ); {
					// Vertex normals
					for ( size_t idx = 0; idx < vertices.size(); ++idx ) {
						glColor3ub( 255, 0, 0 );
						emitVertex( vertices[idx] );
						emitVertex( vertices[idx] + vertexNormals[idx] * (width*2) );
						
						glColor3ub( 255, 255, 255 );
						emitVertex( vertices[idx] );
						emitVertex( vertices[idx] + vertexNormals[idx] * -width );
					}

					// Edge normals
					glColor3ub( 0, 255, 0 );
					for ( size_t idxA = vertices.size()-1, idxB = 0; idxB < vertices.size(); idxA = idxB, ++idxB ) {
						Point2 tmpA = vertices[idxA] + (vertices[idxB]-vertices[idxA])*.5;
						Point2 tmpB = tmpA + edgeNormals[idxA] * (width*2);
						emitVertex( tmpA );
						emitVertex( tmpB );
					}

					// Line-width normals
					glColor3ub( 255, 255, 255 );
					for ( size_t idxA = vertices.size()-1, idxB = 0; idxB < vertices.size(); idxA = idxB, ++idxB ) {
						Point2 tmpA = vertices[idxA] + (vertices[idxB]-vertices[idxA])*.5;
						Point2 tmpB = tmpA + edgeNormals[idxA] * -width;
						emitVertex( tmpA );
						emitVertex( tmpB );
					}
				} glEnd();
			}
		}
	}
}